
import java.awt.Point;
import java.util.ArrayList;

public class PathFinder {
	
	private Map map;
	private Point selectedUnitCoord;
	private Point destinationCoord;
	private ArrayList<Point> reachableArea;
	private ArrayList<ArrayList<Point>> pathsList;
	
	private int reduction;
	
	/*
	 * Get selected unit with given selected unit coordinate
	 */
	private Unit getSelectedUnitByCoord() {
		if(selectedUnitCoord == null)
			return null;
		
		Unit selectedUnit = null;
		for(int i = 0; i < map.getUnitsList().size(); i++)
			if(map.getUnitsList().get(i).getUnitCoord().equals(selectedUnitCoord))
				selectedUnit = map.getUnitsList().get(i);
		return selectedUnit;
	}
	
	/*
	 * Decide the path from origin to destination
	 */
	public void calcPath(Point dC) {
		if(map == null || selectedUnitCoord == null) 
			return;
		
		if(pathsList == null)
			pathsList = new ArrayList<ArrayList<Point>>();
		else
			pathsList.clear();
		
		destinationCoord = dC;	
		Unit unit = getSelectedUnitByCoord();
		ArrayList<Point> path = new ArrayList<Point>();
		path.add(unit.getUnitCoord());
		
		if(destinationCoord.equals(selectedUnitCoord))
			pathsList.add(path);
		else
			searchPath4(path, unit.getMotionPoint());
	}
	
	/*
	 * This method is called when a unit is selected.
	 */
	public void calcReachableArea() {
		if(map == null || selectedUnitCoord == null) 
			return;
		
		if(reachableArea == null)
			reachableArea = new ArrayList<Point>();
		else
			reachableArea.clear();
			
		Unit unit = getSelectedUnitByCoord();
		
		/*
		 * The rest of code lies here. The outcome is assigned into the arrayList reachableArea.
		 * All the points are recommended to be specified in terms of the absolute position rather 
		 * than relative position.
		 * For example: A unit with coordinate (8, 8) can only move to the nearby up, down, left and
		 * right block.
		 * Reachable area in terms of absolute position:
		 * 				up (8, 7)
		 * left (7, 8)				right (9, 8)
		 * 				down (8, 9)
		 */
		search4(unit.getUnitCoord(), unit.getMotionPoint());//unit.getMotionPoint());
	}
	
	/*
	 * Get a block by specified coordinate
	 */
	private Block getBlockByCoord(Point coord) {
		for(int index = 0; index < map.getBlocksList().size(); index++) {
			if(map.getBlocksList().get(index).getBlockCoord().equals(coord))
				return map.getBlocksList().get(index);
		}
		return null;
	}
	
	/*
	 * Mutators
	 */
	public void setSelectedUnitCoord(Point sUC) {
		selectedUnitCoord = sUC;
	}
	
	public void setDestinationCoord(Point dC) {
		destinationCoord = dC;
	}
	
	public void setMap(Map m) {
		map = m;
	}
	
	/*
	 * Accessors
	 */
	public ArrayList<Point> getReachableArea() {		
		return reachableArea;
	}
	
	public ArrayList<Point> getPath() {
		ArrayList<Point> shortestPath = new ArrayList<Point>();
		for(int i = 0; i < pathsList.size(); i++) {
			if(shortestPath.size() == 0 || shortestPath.size() > pathsList.get(i).size())
				shortestPath = pathsList.get(i);		
		}
		return shortestPath;
	}
	
	
	/*
	 * In Class use
	 */
	private void search(Point newpo, int ma){
		if(newpo.x<0||newpo.x>map.getWidth()-1||newpo.y<0||newpo.y>map.getHeight()-1){
			reduction = -99;
		}
		else {
			Unit unit = getSelectedUnitByCoord();
			if(unit.getUnitTypeID() == Unit.UNIT_TYPE_ID_TANK)
				reduction = getBlockByCoord(newpo).getMotionPointReductionForTank();
			else if(unit.getUnitTypeID() == Unit.UNIT_TYPE_ID_MOBILEAA)
				reduction = getBlockByCoord(newpo).getMotionPointReductionForMobileAA();
			else if(unit.getUnitTypeID() == Unit.UNIT_TYPE_ID_BATTLESHIP)
				reduction = getBlockByCoord(newpo).getMotionPointReductionForBattleship();
			else if(unit.getUnitTypeID() == Unit.UNIT_TYPE_ID_INFANTRY)
				reduction = getBlockByCoord(newpo).getMotionPointReductionForInfantry();
			else if(unit.getUnitTypeID() == Unit.UNIT_TYPE_ID_JET)
				reduction = getBlockByCoord(newpo).getMotionPointReductionForJet();
		}
		
		for(int i=0;i<map.getUnitsList().size();i++){
			if(map.getUnitsList().get(i).unitCoord.equals(newpo)&& map.getUnitsList().get(i).unitCoord.equals(selectedUnitCoord)==false){
				reduction = -99;
				break;
			}
		}
		
		if(ma + reduction >= 0){
			if(reachableArea.contains(newpo)!=true)
				reachableArea.add(newpo);
			search4(newpo,ma + reduction);
		}
	}
	
	private void search4(Point po, int ma){
		search(new Point(po.x,po.y+1),ma);
		search(new Point(po.x-1,po.y),ma);
		search(new Point(po.x,po.y-1),ma);
		search(new Point(po.x+1,po.y),ma);
	}
	
	private void searchPath(ArrayList<Point> path, int ma) {
		/*
		 * Check map bounds
		 */
		if(path.get(path.size() - 1).x < 0 || 
				path.get(path.size() - 1).x > map.getWidth() - 1 ||
				path.get(path.size() - 1).y < 0 ||
				path.get(path.size() - 1).y > map.getHeight() - 1) {
			reduction = -99;
		}
		else {
			Unit unit = getSelectedUnitByCoord();
			if(unit.getUnitTypeID() == Unit.UNIT_TYPE_ID_TANK)
				reduction = getBlockByCoord(path.get(path.size() - 1)).getMotionPointReductionForTank();
			else if(unit.getUnitTypeID() == Unit.UNIT_TYPE_ID_MOBILEAA)
				reduction = getBlockByCoord(path.get(path.size() - 1)).getMotionPointReductionForMobileAA();
			else if(unit.getUnitTypeID() == Unit.UNIT_TYPE_ID_BATTLESHIP)
				reduction = getBlockByCoord(path.get(path.size() - 1)).getMotionPointReductionForBattleship();
			else if(unit.getUnitTypeID() == Unit.UNIT_TYPE_ID_INFANTRY)
				reduction = getBlockByCoord(path.get(path.size() - 1)).getMotionPointReductionForInfantry();
			else if(unit.getUnitTypeID() == Unit.UNIT_TYPE_ID_JET)
				reduction = getBlockByCoord(path.get(path.size() - 1)).getMotionPointReductionForJet();
		}
		
		/*
		 * Check the coordinates of other units
		 */
		for(int i = 0; i < map.getUnitsList().size(); i++){
			if(map.getUnitsList().get(i).unitCoord.equals(path.get(path.size() - 1)) && 
					map.getUnitsList().get(i).unitCoord.equals(selectedUnitCoord) == false) {
				reduction = -99;
				break;
			}
		}
		
		if(ma + reduction >= 0){
			if(path.get(path.size() - 1).equals(destinationCoord)) {
				for(int i = 0; i < path.size(); i++) {
					System.out.print("(" + path.get(i).x + "," + path.get(i).y + ") ");
				}
				System.out.println();
				pathsList.add(path);
				return;
			}
			searchPath4(path, ma + reduction);
		}
	}
	
	private void searchPath4(ArrayList<Point> path, int ma) {
		ArrayList<Point> pathUp = new ArrayList<Point>(path);
		pathUp.add(new Point(path.get(path.size() - 1).x, path.get(path.size() - 1).y + 1));
		searchPath(pathUp, ma);
		
		ArrayList<Point> pathLeft = new ArrayList<Point>(path);
		pathLeft.add(new Point(path.get(path.size() - 1).x - 1, path.get(path.size() - 1).y));
		searchPath(pathLeft, ma);
		
		ArrayList<Point> pathDown = new ArrayList<Point>(path);
		pathDown.add(new Point(path.get(path.size() - 1).x, path.get(path.size() - 1).y - 1));
		searchPath(pathDown, ma);
		
		ArrayList<Point> pathRight = new ArrayList<Point>(path);
		pathRight.add(new Point(path.get(path.size() - 1).x + 1, path.get(path.size() - 1).y));
		searchPath(pathRight, ma);
	}
}
